package com.metadata_system.api.inspect;


import com.alibaba.fastjson.JSON;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.metadata_system.api.annotations.Limit;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import com.metadata_system.api.dto.LimitItem;
import com.metadata_system.api.exceptions.LimitException;
import com.metadata_system.common.exception.ApiException;
import com.metadata_system.common.utils.AddressUtil;
import com.metadata_system.common.utils.AesUtils;
import com.metadata_system.common.utils.RedisUtil;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;


@Aspect
@Configuration
@Slf4j
public class LimitInterceptor {


    @Autowired
    RedisUtil redisUtil;

    // 最大限制秒数
    public static int MaxLimitSeconds = 60;
    // 60秒内所有url最大访问次数
    public static int MaxLimitNums = 50;
    public static String REDIS_PRE = "DEVICE_ACCESS_";

    @Around("execution(public * *(..)) && @annotation(com.metadata_system.api.annotations.Limit)")
    public Object interceptor(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        Limit limitAnno = method.getAnnotation(Limit.class);

        // 1.获取 request
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();


        // 2.根据key值限流
        String limitKey = "";
        if (limitAnno.type().equals("Deviceid")) {
            try {
                if (StringUtils.isBlank(request.getHeader("Deviceid"))) {
                    throw new ApiException("非法数据 1");
                }
                String str = AesUtils.Decrypt(request.getHeader("Deviceid"));
            } catch (Exception e) {
                throw new ApiException("非法数据 3: "+request.getHeader("Deviceid"));
            }
            limitKey = request.getHeader("Deviceid");
        }
        if (StringUtils.isBlank(limitAnno.type()) || limitAnno.type().equals("IP")) {
            limitKey = AddressUtil.GetAddressIp();
        }


        if (!this.checkAccess(limitKey, request.getRequestURI(), limitAnno.seconds(), limitAnno.count())) {
            throw new LimitException("访问频率过高");
        }

        this.addAccess(limitKey, request.getRequestURI());
        return pjp.proceed();
    }

    /**
     * 获取用户请求数据
     *
     * @return lua脚本
     */
    private List<LimitItem> getDeviceAccessList(String deviceId) {
        String listJSONString = redisUtil.getValue(REDIS_PRE + deviceId, String.class);
        List<LimitItem> limitItemList = new ArrayList<>();
        if (StringUtils.isBlank(listJSONString)) {
            return limitItemList;
        }
        limitItemList = JSON.parseArray(listJSONString, LimitItem.class);
        return limitItemList;
    }


    private Boolean checkAccess(String deviceId, String url, int seconds, int maxNums) {
        // 2个值格式错误，则进行校验
        if (seconds <= 0 || maxNums <= 0) {
            return true;
        }
        if (seconds > MaxLimitSeconds) {
            seconds = MaxLimitSeconds;
        }
        if (maxNums > MaxLimitNums) {
            maxNums = MaxLimitNums;
        }

        List<LimitItem> deviceAccessList = this.getDeviceAccessList(deviceId);
        long currTime = new Date().getTime();

        if (deviceAccessList.size() > MaxLimitNums) {
            LimitItem lastItem = deviceAccessList.get(0);
            // 第一个访问是1分钟内的，那么就说明超过总体限制: 100/分钟
            long diff = currTime - lastItem.getAccessTime().getTime();
            if (diff < 60000L) {
                System.out.println("return false");
                return false;
            }
        }

        int i = 0;
        for (LimitItem limitItem : deviceAccessList) {
            if (limitItem.getUrl().equals(url) && Math.abs(currTime - limitItem.getAccessTime().getTime()) < seconds * 1000L) {
                i++;
            }
        }
        return i < maxNums;
    }


    /**
     * 增加访问记录
     *
     * @param deviceId
     * @param url
     */
    private void addAccess(String deviceId, String url) {
        List<LimitItem> deviceAccessList = this.getDeviceAccessList(deviceId);
        deviceAccessList.add(LimitItem.builder()
                .DeviceID(deviceId)
                .Url(url)
                .accessTime(new Date())
                .build());
        if (deviceAccessList.size() > MaxLimitNums + 10) {
            deviceAccessList = deviceAccessList.subList(deviceAccessList.size() - MaxLimitNums + 5, deviceAccessList.size() - 1);
        }
        redisUtil.setValue(REDIS_PRE + deviceId, JSON.toJSONString(deviceAccessList));
    }


}
